# -*- coding: utf-8 -*-
#
# Copyright (c) 2012, Rafael Pivato
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Rafael Pivato nor the names of its contributors
#       may be used to endorse or promote products derived from this
#       software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL RAFAEL PIVATO BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import cgi

from wowgae.link import EmailLinkHelper
from wowgae.link import WowLinkHelper
from wowgae.link import YoutubeLinkHelper
from wowgae.link import HttpLinkHelper
from wowgae.link import WikiLinkHelper
from wowgae.link import InvalidLinkHelper


class MacroBase(object):
    """Base class for wiki text macros."""

    def __init__(self, parser):
        self.parser = parser
        self.scanner = parser.scanner
        self.writer = parser.writer
        self._init_macro()

    def _init_macro(self):
        """Performs macro specific initialization"""
        pass

    def process(self):
        """Processes the content using this macro algorithm."""
        return False


class InlineMacro(MacroBase):

    def match_and_process(self, section):
        if self.match(section):
            self.process(section)
            return True
        else:
            return False

    def process(self, section):
        """Processes current inline macro."""
        pass

    def match(self, section):
        """Check if the macro matches in the current position."""
        return False


class CgiEscapeInlineMacro(InlineMacro):
    """Deals with single characters escape for as an inline macro."""

    def _init_macro(self):
        """Initializes the inline macro."""
        pass

    def match(self, section):
        """This is to be used as a default inline macro."""
        return True

    def process(self, section):
        """Processes one single character escaping it."""
        self.writer.write(cgi.escape(self.scanner.read()))


class OnOffMacro(InlineMacro):
    """Represents a macro that turns on and off a condition inline."""

    def _init_macro(self):
        """It will start off by default."""
        self.on = False

    def token(self):
        """Returns the code for this OnOff macro, defaults to None."""
        return None

    def turn(self):
        """Processes current on/off state of the macro just switched."""
        pass

    def match(self, section):
        """Test if we can find the macro code at current position."""
        code = self.token()
        len_code = len(code)
        peek = self.scanner.peek(len_code)
        if peek == code:
            return True

    def process(self, section):
        """Processes current switch turning it on and off accordingly."""
        self.scanner.skip(len(self.token()))
        self.on = not self.on
        self.turn()


class ItalicMacro(OnOffMacro):
    """Represents the macro for italic style."""

    def token(self):
        """Returns the macro code for italic signaling."""
        return "''"

    def turn(self):
        """Processes italic or or off."""
        if self.on:
            tag = '<i>'
        else:
            tag = '</i>'
        self.writer.write(tag)


class BoldMacro(OnOffMacro):
    """Represents the macro for bold weight."""

    def token(self):
        """Returns the macro code for bold signaling."""
        return "'''"

    def turn(self):
        """Processes bold or or off."""
        if self.on:
            tag = '<b>'
        else:
            tag = '</b>'
        self.writer.write(tag)


class InlineMacroSequence(InlineMacro):
    """Represents a sequence of inline macros."""

    def _init_macro(self):
        self.matched = None
        self.sequence = self.init_sequence()

    def init_sequence(self):
        return []

    def match(self, section):
        """Tries to match in the given order."""
        for macro in self.sequence:
            if macro.match(section):
                self.matched = macro
                return True
        return False

    def process(self, section):
        """Processes the matched section."""
        self.matched.process(section)


class TextStyleMacro(InlineMacroSequence):
    """Represents a sequence of text style macros."""

    def init_sequence(self):
        parser = self.parser
        return [BoldMacro(parser), ItalicMacro(parser)]


class OpenCloseMacro(InlineMacro):
    """Abstract macro for inline macros with open and close tokens."""

    def __peek_and_skip_closing(self):
        size = len(self.closing)
        if self.scanner.peek(size) == self.closing:
            self.scanner.skip(size)
            return True
        return False

    def __peek_opening(self):
        size = len(self.opening)
        return self.scanner.peek(size) == self.opening

    def __skip_opening(self):
        size = len(self.opening)
        self.scanner.skip(size)

    def __peek_eol(self):
        return self.scanner.peek() in '\r\n'

    def __read_value(self):
        """Reads value until next closing token."""
        value = ''
        while not self.__peek_eol() and not self.scanner.eof():
            if self.__peek_and_skip_closing():
                break
            else:
                value += self.scanner.read()
        return value

    def set_token(self, opening, closing=None):
        """Defines opening and closing tokens."""
        if closing is None:
            closing = opening
        self.opening = opening
        self.closing = closing

    def match(self, section=None):
        """Tries to match the open tag."""
        return self.__peek_opening()

    def process(self, previous=None):
        """Processes this open/close macro."""
        self.__skip_opening()
        value = self.__read_value()
        self.process_value(value)


class GeneralLinkMacro(OpenCloseMacro):
    """Processes general macros."""

    def _init_macro(self):
        """Initialize this page link macro."""
        self.set_token('[[', ']]')
        self.helpers = [
            EmailLinkHelper(),
            WowLinkHelper(),
            YoutubeLinkHelper(),
            HttpLinkHelper(),
            WikiLinkHelper(),
            InvalidLinkHelper(),
        ]

    def process_value(self, value):
        """Processes this link."""
        for helper in self.helpers:
            if helper.match(value):
                self.writer.write(helper.match_html)
                break


class SectionMacro(MacroBase):

    def match_and_process(self, section, previous):
        if self.match(section, previous):
            self.process(section, previous)
            return True
        else:
            return False

    def process(self, section, previous):
        """Processes current inline macro."""
        pass

    def match(self, section, previous):
        """Check if the macro matches in the current position."""
        return False

    def process_eod(self):
        """Gives the opportunity for sections to do some finalization."""
        pass


class InstructionSectionMacro(SectionMacro):
    """Deals with instruction placed at the top of a wiki page."""

    def process(self, section, previous):
        """Processes a instruction."""
        while not self.scanner.eof() and self.scanner.read() != '\n':
            pass

    def match(self, section, previous):
        """Check if the macro matches in the current position."""
        return self.scanner.peek() == '!'


class HeadingMacro(SectionMacro):
    """Processes section heading wiki text macro."""

    def _init_macro(self):
        """Initializes this heading macro."""
        self.open_sections = []

    def __close_open_sections(self, current_level):
        """Closes previous open sections."""
        total_sections = len(self.open_sections)
        for i in reversed(range(total_sections)):
            open_level = self.open_sections[i]
            if open_level >= current_level:
                self.__close_heading(open_level)
                self.open_sections.pop(i)
            else:
                break

    def __close_heading(self, level):
        """Writes closing tag."""
        closing = '</section><!-- level %s -->' % level
        self.writer.write(closing)

    def __peek_heading_token(self):
        """Tries to peek a heading token returning its level or zero."""
        max_level = 6
        peek = self.scanner.peek(max_level)
        for level in range(max_level, 1, -1):
            pattern = '=' * level
            if peek.startswith(pattern):
                return level
        return 0

    def __read_heading(self, level):
        """Reads the heading text from the current stream."""
        pattern = '=' * level
        finished = False
        heading = ''
        while self.scanner.peek() not in '\r\n' and not self.scanner.eof():
            if self.scanner.peek(level) == pattern:
                self.scanner.skip(level)
                finished = True
            elif finished:
                self.scanner.skip()
            else:
                heading += self.scanner.read()
        return heading

    def __open_heading(self, heading):
        """Writes opening tags for the heading."""
        opening = '<section><h1>%s</h1>' % heading
        self.writer.write(opening)

    def match(self, section, previous):
        """Tries to match against an existing heading section."""
        return self.__peek_heading_token()

    def process(self, section, previous):
        """Looks for a heading macro at current position."""
        level = self.__peek_heading_token()
        self.scanner.skip(level)
        self.__close_open_sections(level)
        heading = self.__read_heading(level)
        self.open_sections.append(level)
        self.__open_heading(heading)

    def process_eod(self):
        """Closes open sections."""
        for level in self.open_sections:
            self.__close_heading(level)


class BlockSectionMacro(SectionMacro):
    """Deals with general open and close section macros."""

    def open_tag(self):
        if self.tag():
            return '<%s>' % (self.tag())
        return ''

    def close_tag(self):
        if self.tag():
            return '</%s>' % (self.tag())
        return ''

    def inline(self):
        return True

    def tag(self):
        return 'tag'

    def process_open(self, section, previous):
        """Processes the token opening this section."""
        pass

    def process_close(self, section, previous):
        """Processes the end of a section."""
        pass

    def process(self, section, previous):
        """Processes a paragraph section."""
        tag_open = False
        self.process_open(section, previous)
        while not self.scanner.eof():
            peek = self.scanner.peek()
            if peek == '\n':
                self.scanner.skip()
                if not tag_open:
                    break
                elif not self.resume(section, previous):
                    self.process_close(section, previous)
                    break
            elif not tag_open and not peek.isspace():
                self.writer.write(self.open_tag())
                tag_open = True
            if self.inline():
                self.parser.inline_macros.match_and_process(section)
            else:
                self.parser.default_inline.process(section)
        if tag_open:
            self.writer.write(self.close_tag())

    def match(self, section, previous):
        """Tries to match the start of a block."""
        return False

    def resume(self, section, previous):
        """After every line break, checks if the block continues."""
        if self.match(section, previous):
            self.process_open(section, previous)
            return True
        else:
            peek = self.scanner.peek(20)
            for c in peek:
                if c == '\n':
                    return False
                if not c.isspace():
                    return True
        return False


class PreformattedSectionMacro(BlockSectionMacro):

    def inline(self):
        return False

    def tag(self):
        return 'pre'

    def process_open(self, section, previous):
        """Processes the token opening this section."""
        self.scanner.skip(2)

    def match(self, section, previous):
        peek = self.scanner.peek(2)
        if peek == '  ':
            return True
        else:
            return False

    def resume(self, section, previous):
        """Need the token for every line."""
        if self.match(section, previous):
            self.process_open(section, previous)
            return True
        return False


class ParagraphSectionMacro(BlockSectionMacro):

    def tag(self):
        return 'p'

    def match(self, section, previous):
        return True

    def resume(self, section, previous):
        """After every line break, checks if the block continues."""
        peek = self.scanner.peek(20)
        for c in peek:
            if c == '\n':
                return False
            if not c.isspace():
                return True
        return False


class QuoteSectionMacro(BlockSectionMacro):

    def tag(self):
        return 'blockquote'

    def process_open(self, section, previous):
        """Processes the token opening this section."""
        self.scanner.skip(2)

    def match(self, section, previous):
        peek = self.scanner.peek(2)
        if peek == '" ':
            return True
        else:
            return False

    def resume(self, section, previous):
        """Need the token for every line."""
        if self.match(section, previous):
            self.process_open(section, previous)
            return True
        return False


class OldListSectionMacro(BlockSectionMacro):
    """Deals with ordered and unordered lists."""

    def _init_macro(self):
        """Initializes this paragraph macro."""
        self.max_level = 6
        self.tags = {'#': 'ol', '*': 'ul'}
        self.open = []

    def __open_level(self, kind, level):
        """Processes an ordered list."""
        self.writer.write('<%s>' % (self.tags[kind]))
        self.open.append(kind)

    def __close_levels(self, count):
        for _ in range(count):
            kind = self.open.pop()
            self.writer.write('</%s>' % (self.tags[kind]))

    def __process_item(self):
        self.writer.write('<li>')
        while not self.scanner.eof():
            peek = self.scanner.peek()
            if peek == '\n':
                self.scanner.skip()
                break
            self.parser.process_inlines()
        self.writer.write('</li>')

    def __peek_list_token(self):
        """Tries to peek the list token returning its level and type."""
        max_level = 6
        peek = self.scanner.peek(max_level)
        for level in range(max_level, 0, -1):
            for kind in ['#', '*']:
                pattern = kind * level + ' '
                if peek.startswith(pattern):
                    return (kind, level)
        return (None, None)

    def process(self, section, previous):
        """Processes the list."""
        current = 0
        kind, level = self.__peek_list_token()
        if not kind or level != 1:
            return False
        while kind:
            if level == current:
                self.scanner.skip(level + 1)
            elif level == current + 1:
                self.scanner.skip(level + 1)
                self.__open_level(kind, level)
            elif level < current:
                self.scanner.skip(level + 1)
                self.__close_levels(current - level)
            else:
                break
            self.__process_item()
            current = level
            kind, level = self.__peek_list_token()
        self.__close_levels(current)
        return False

    def match(self, section, previous):
        """Tries to match the start of a list."""
        self.kind, self.level = self.__peek_list_token()
        if not self.kind or self.level != 1:
            return False
        return True


class ListSectionMacro(BlockSectionMacro):
    """Deals with ordered and unordered lists."""

    def _init_macro(self):
        """Initializes this paragraph macro."""
        self.current = 0
        self.open = []
        self.max_level = 6
        self.tags = {'#': 'ol', '*': 'ul'}

    def __open_level(self, kind, level):
        """Processes an ordered list."""
        self.writer.write('<%s>' % (self.tags[kind]))
        self.open.append(kind)

    def __close_levels(self, count):
        for _ in range(count):
            kind = self.open.pop()
            self.writer.write('</%s>' % (self.tags[kind]))

    def __peek_list_token(self):
        """Tries to peek the list token returning its level and type."""
        max_level = 6
        peek = self.scanner.peek(max_level)
        for level in range(max_level, 0, -1):
            for kind in ['#', '*']:
                pattern = kind * level + ' '
                if peek.startswith(pattern):
                    return (kind, level)
        return (None, None)

    def tag(self):
        return ''

    def process_open(self, section, previous):
        if self.level == self.current:
            self.scanner.skip(self.level + 1)
        elif self.level == self.current + 1:
            self.scanner.skip(self.level + 1)
            self.__open_level(self.kind, self.level)
        elif self.level < self.current:
            self.scanner.skip(self.level + 1)
            self.__close_levels(self.current - self.level)
        self.writer.write('<li>')
        self.current = self.level

    def process_close(self, section, previous):
        self.__close_levels(self.current)
        self.current = 0
        self.open = []

    def rrresume(self, section, previous):
        if self.match(section, previous):
            self.process_open(section, previous)
            return True

        new_kind, new_level = self.__peek_list_token()
        if new_kind:
            if new_level == self.current or \
            new_level == self.current + 1 or \
            new_level < self.current:
                self.level = new_level
                self.kind = new_kind
                return True
            else:
                return False
        peek = self.scanner.peek(20)
        for c in peek:
            if c == '\n':
                return False
            if not c.isspace():
                return True
        return False

    def match(self, section, previous):
        """Tries to match the start of a list."""
        new_kind, new_level = self.__peek_list_token()
        if new_kind:
            if new_level == self.current or \
            new_level == self.current + 1 or \
            new_level < self.current:
                self.level = new_level
                self.kind = new_kind
                return True
            else:
                return False
        return False


class SectionMacroSequence(SectionMacro):
    """Represents a sequence of section macros."""

    def _init_macro(self):
        self.matched = None
        self.sequence = self.init_sequence()

    def init_sequence(self):
        return []

    def match(self, section, previous):
        """Tries to match in the given order."""
        for macro in self.sequence:
            if macro.match(section, previous):
                self.matched = macro
                return True
        return False

    def process(self, section, previous):
        """Processes the matched section."""
        self.matched.process(section, previous)

    def process_eod(self):
        """Lets the macro handle end of document."""
        for macro in self.sequence:
            macro.process_eod()


class GeneralSectionMacro(SectionMacroSequence):
    """Represents a sequence of our common macros."""

    def init_sequence(self):
        parser = self.parser
        return [
            HeadingMacro(parser),
            PreformattedSectionMacro(parser),
            QuoteSectionMacro(parser),
            ListSectionMacro(parser),
            ParagraphSectionMacro(parser),
        ]


class GeneralInlineMacro(InlineMacroSequence):
    """Represents a sequence of our common inline macros."""

    def init_sequence(self):
        parser = self.parser
        return [
            TextStyleMacro(parser),
            GeneralLinkMacro(parser),
            CgiEscapeInlineMacro(parser),
        ]
